/****************************************************************************
 * drivers/sercomm/uart.c
 * Calypso DBB internal UART Driver
 *
 * (C) 2010 by Harald Welte <laforge@gnumonks.org>
 * (C) 2010 by Ingo Albrecht <prom@berlin.ccc.de>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 **************************************************************************/

#include <stdint.h>
#include <string.h>
#include <stdio.h>

#include <nuttx/config.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>

#include <arch/calypso/memory.h>
#include <arch/calypso/debug.h>

#include <arch/calypso/defines.h>
//#include <arch/calypso/console.h>
#include <nuttx/sercomm/sercomm.h>

#include "uart.h"

#define BASE_ADDR_UART_MODEM  0xffff5000
#define OFFSET_IRDA    0x800

#define UART_REG(n,m)  (BASE_ADDR_UART_MODEM + ((n)*OFFSET_IRDA)+(m))

#define LCR7BIT        0x80
#define LCRBFBIT       0x40
#define MCR6BIT        0x20
#define REG_OFFS(m)    ((m) & ~(LCR7BIT|LCRBFBIT|MCR6BIT))

/* read access LCR[7] = 0 */

enum uart_reg
{
  RHR  = 0,
  IER  = 1,
  IIR  = 2,
  LCR  = 3,
  MCR  = 4,
  LSR  = 5,
  MSR  = 6,
  SPR  = 7,
  MDR1  = 8,
  DMR2  = 9,
  SFLSR  = 0x0a,
  RESUME  = 0x0b,
  SFREGL  = 0x0c,
  SFREGH  = 0x0d,
  BLR  = 0x0e,
  ACREG  = 0x0f,
  SCR  = 0x10,
  SSR  = 0x11,
  EBLR  = 0x12,

  /* read access LCR[7] = 1 */

  DLL  = RHR | LCR7BIT,
  DLH  = IER | LCR7BIT,
  DIV1_6  = ACREG | LCR7BIT,

  /* read/write access LCR[7:0] = 0xbf */

  EFR  = IIR | LCRBFBIT,
  XON1  = MCR | LCRBFBIT,
  XON2  = LSR | LCRBFBIT,
  XOFF1  = MSR | LCRBFBIT,
  XOFF2   = SPR | LCRBFBIT,

  /* read/write access if EFR[4] = 1 and MCR[6] = 1 */

  TCR  = MSR | MCR6BIT,
  TLR  = SPR | MCR6BIT,
};

/* write access LCR[7] = 0 */

#define THR    RHR
#define FCR    IIR    /* only if EFR[4] = 1 */
#define TXFLL  SFLSR
#define TXFLH  RESUME
#define RXFLL  SFREGL
#define RXFLH  SFREGH

enum fcr_bits
{
  FIFO_EN        = (1 << 0),
  RX_FIFO_CLEAR  = (1 << 1),
  TX_FIFO_CLEAR  = (1 << 2),
  DMA_MODE       = (1 << 3),
};

#define TX_FIFO_TRIG_SHIFT  4
#define RX_FIFO_TRIG_SHIFT  6

enum iir_bits
{
  IIR_INT_PENDING              = 0x01,
  IIR_INT_TYPE                 = 0x3E,
  IIR_INT_TYPE_RX_STATUS_ERROR = 0x06,
  IIR_INT_TYPE_RX_TIMEOUT      = 0x0C,
  IIR_INT_TYPE_RHR             = 0x04,
  IIR_INT_TYPE_THR             = 0x02,
  IIR_INT_TYPE_MSR             = 0x00,
  IIR_INT_TYPE_XOFF            = 0x10,
  IIR_INT_TYPE_FLOW            = 0x20,
  IIR_FCR0_MIRROR              = 0xC0,
};

#define UART_REG_UIR  0xffff6000

/* enable or disable the divisor latch for access to DLL, DLH */

static void uart_set_lcr7bit(int uart, int on)
{
  uint8_t reg;

  reg = readb(UART_REG(uart, LCR));
  if (on)
    {
      reg |= (1 << 7);
    }
  else
    {
      reg &= ~(1 << 7);
    }

  writeb(reg, UART_REG(uart, LCR));
}

static uint8_t old_lcr;
static void uart_set_lcr_bf(int uart, int on)
{
  if (on)
    {
      old_lcr = readb(UART_REG(uart, LCR));
      writeb(0xBF, UART_REG(uart, LCR));
    }
  else
    {
      writeb(old_lcr, UART_REG(uart, LCR));
    }
}

/* Enable or disable the TCR_TLR latch bit in MCR[6] */

static void uart_set_mcr6bit(int uart, int on)
{
  uint8_t mcr;

  /* we assume EFR[4] is always set to 1 */

  mcr = readb(UART_REG(uart, MCR));
  if (on)
    {
      mcr |= (1 << 6);
    }
  else
    {
      mcr &= ~(1 << 6);
    }

  writeb(mcr, UART_REG(uart, MCR));
}

static void uart_reg_write(int uart, enum uart_reg reg, uint8_t val)
{
  if (reg & LCRBFBIT)
    {
      uart_set_lcr_bf(uart, 1);
    }
  else if (reg & LCR7BIT)
    {
      uart_set_lcr7bit(uart, 1);
    }
  else if (reg & MCR6BIT)
    {
      uart_set_mcr6bit(uart, 1);
    }

  writeb(val, UART_REG(uart, REG_OFFS(reg)));

  if (reg & LCRBFBIT)
    {
      uart_set_lcr_bf(uart, 0);
    }
  else if (reg & LCR7BIT)
    {
      uart_set_lcr7bit(uart, 0);
    }
  else if (reg & MCR6BIT)
    {
      uart_set_mcr6bit(uart, 0);
    }
}

/* read from a UART register, applying any required latch bits */

static uint8_t uart_reg_read(int uart, enum uart_reg reg)
{
  uint8_t ret;

  if (reg & LCRBFBIT)
    {
     uart_set_lcr_bf(uart, 1);
    }
  else if (reg & LCR7BIT)
    {
      uart_set_lcr7bit(uart, 1);
    }
  else if (reg & MCR6BIT)
    {
      uart_set_mcr6bit(uart, 1);
    }

  ret = readb(UART_REG(uart, REG_OFFS(reg)));

  if (reg & LCRBFBIT)
    {
      uart_set_lcr_bf(uart, 0);
    }
  else if (reg & LCR7BIT)
    {
      uart_set_lcr7bit(uart, 0);
    }
  else if (reg & MCR6BIT)
    {
      uart_set_mcr6bit(uart, 0);
    }

  return ret;
}

#if 0
static void uart_irq_handler_cons(__unused enum irq_nr irqnr)
{
  const uint8_t uart = CONS_UART_NR;
  uint8_t iir;

  //uart_putchar_nb(uart, 'U');

  iir = uart_reg_read(uart, IIR);
  if (iir & IIR_INT_PENDING)
    {
      return;
    }

  switch (iir & IIR_INT_TYPE)
    {
    case IIR_INT_TYPE_RHR:
      break;

    case IIR_INT_TYPE_THR:
      if (cons_rb_flush() == 1)
        {
          /* everything was flushed, disable THR IRQ */

          uint8_t ier = uart_reg_read(uart, IER);
          ier &= ~(1 << 1);
          uart_reg_write(uart, IER, ier);
        }
      break;

    case IIR_INT_TYPE_MSR:
      break;

    case IIR_INT_TYPE_RX_STATUS_ERROR:
      break;

    case IIR_INT_TYPE_RX_TIMEOUT:
      break;

    case IIR_INT_TYPE_XOFF:
      break;
  }
}
#endif

static void uart_irq_handler_sercomm(__unused enum irq_nr irqnr, __unused void *context)
{
  const uint8_t uart = SERCOMM_UART_NR;
  uint8_t iir, ch;

  //uart_putchar_nb(uart, 'U');

  iir = uart_reg_read(uart, IIR);
  if (iir & IIR_INT_PENDING)
    {
      return;
    }

  switch (iir & IIR_INT_TYPE)
    {
    case IIR_INT_TYPE_RX_TIMEOUT:
    case IIR_INT_TYPE_RHR:
      /* as long as we have rx data available */

      while (uart_getchar_nb(uart, &ch))
        {
          if (sercomm_drv_rx_char(ch) < 0)
            {
              /* sercomm cannot receive more data right now */

              uart_irq_enable(uart, UART_IRQ_RX_CHAR, 0);
            }
        }
      break;

    case IIR_INT_TYPE_THR:
      /* as long as we have space in the FIFO */

      while (!uart_tx_busy(uart))
        {
          /* get a byte from sercomm */

          if (!sercomm_drv_pull(&ch))
            {
              /* no more bytes in sercomm, stop TX interrupts */

              uart_irq_enable(uart, UART_IRQ_TX_EMPTY, 0);
              break;
            }

          /* write the byte into the TX FIFO */

          uart_putchar_nb(uart, ch);
        }
      break;

    case IIR_INT_TYPE_MSR:
      printf("UART IRQ MSR\n");
      break;

    case IIR_INT_TYPE_RX_STATUS_ERROR:
      printf("UART IRQ RX_SE\n");
      break;

    case IIR_INT_TYPE_XOFF:
      printf("UART IRQXOFF\n");
      break;
    }
}

static const uint8_t uart2irq[] =
{
  [0]  = IRQ_UART_IRDA,
  [1]  = IRQ_UART_MODEM,
};

void uart_init(uint8_t uart, uint8_t interrupts)
{
#if 0
  uint8_t irq = uart2irq[uart];
#endif

  uart_reg_write(uart, IER, 0x00);

  if (uart == SERCOMM_UART_NR)
    {
      sercomm_init();
      irq_attach(IRQ_UART_MODEM, (xcpt_t)uart_irq_handler_sercomm);
      up_enable_irq(IRQ_UART_MODEM);
      uart_irq_enable(uart, UART_IRQ_RX_CHAR, 1);
    }

#if 0
  if (uart == CONS_UART_NR)
    {
      cons_init();
      if (interrupts)
        {
          irq_register_handler(irq, &uart_irq_handler_cons);
          irq_config(irq, 0, 0, 0xff);
          irq_enable(irq);
        }
    }
  else
    {
      sercomm_init();
      if (interrupts)
        {
          irq_register_handler(irq, &uart_irq_handler_sercomm);
          irq_config(irq, 0, 0, 0xff);
          irq_enable(irq);
        }

      uart_irq_enable(uart, UART_IRQ_RX_CHAR, 1);
    }
#endif
#if 0
  if (uart == 1)
    {
      /* assign UART to MCU and unmask interrupts*/

      writeb(UART_REG_UIR, 0x00);
    }
#endif

  /* if we don't initialize these, we get strange corruptions in the
   * received data... :-(
   */

  uart_reg_write(uart,  MDR1, 0x07); /* turn off UART */
  uart_reg_write(uart,  XON1, 0x00); /* Xon1/Addr Register */
  uart_reg_write(uart,  XON2, 0x00); /* Xon2/Addr Register */
  uart_reg_write(uart, XOFF1, 0x00); /* Xoff1 Register */
  uart_reg_write(uart, XOFF2, 0x00); /* Xoff2 Register */
  uart_reg_write(uart,   EFR, 0x00); /* Enhanced Features Register */

  /* select  UART mode */

  uart_reg_write(uart, MDR1, 0);

  /* no XON/XOFF flow control, ENHANCED_EN, no auto-RTS/CTS */

  uart_reg_write(uart, EFR, (1 << 4));

  /* enable Tx/Rx FIFO, Tx trigger at 56 spaces, Rx trigger at 60 chars */

  uart_reg_write(uart, FCR, FIFO_EN | RX_FIFO_CLEAR | TX_FIFO_CLEAR |
                (3 << TX_FIFO_TRIG_SHIFT) | (3 << RX_FIFO_TRIG_SHIFT));

  /* THR interrupt only when TX FIFO and TX shift register are empty */

  uart_reg_write(uart, SCR, (1 << 0));// | (1 << 3));

  /* 8 bit, 1 stop bit, no parity, no break */

  uart_reg_write(uart, LCR, 0x03);

  uart_set_lcr7bit(uart, 0);
}

void uart_poll(uint8_t uart)
{
#if 0
  if (uart == CONS_UART_NR)
    {
      uart_irq_handler_cons(0);
    }
  else
#endif
    {
      uart_irq_handler_sercomm(0, NULL);
    }
}

void uart_irq_enable(uint8_t uart, enum uart_irq irq, int on)
{
  uint8_t ier = uart_reg_read(uart, IER);
  uint8_t mask = 0;

  switch (irq)
    {
    case UART_IRQ_TX_EMPTY:
      mask = (1 << 1);
      break;

    case UART_IRQ_RX_CHAR:
      mask = (1 << 0);
      break;
    }

  if (on)
    {
      ier |= mask;
    }
  else
    {
      ier &= ~mask;
    }

  uart_reg_write(uart, IER, ier);
}

void uart_putchar_wait(uint8_t uart, int c)
{
  /* wait while TX FIFO indicates full */

  while (readb(UART_REG(uart, SSR)) & 0x01) { }

  /* put character in TX FIFO */

  writeb(c, UART_REG(uart, THR));
}

int uart_putchar_nb(uint8_t uart, int c)
{
  /* if TX FIFO indicates full, abort */

  if (readb(UART_REG(uart, SSR)) & 0x01)
    {
      return 0;
    }

  writeb(c, UART_REG(uart, THR));
  return 1;
}

int uart_getchar_nb(uint8_t uart, uint8_t *ch)
{
  uint8_t lsr;

  lsr = readb(UART_REG(uart, LSR));

  /* something strange happened */

  if (lsr & 0x02)
    {
      printf("LSR RX_OE\n");
    }

  if (lsr & 0x04)
    {
      printf("LSR RX_PE\n");
    }

  if (lsr & 0x08)
    {
      printf("LSR RX_FE\n");
    }

  if (lsr & 0x10)
    {
      printf("LSR RX_BI\n");
    }

  if (lsr & 0x80)
    {
      printf("LSR RX_FIFO_STS\n");
    }

  /* is the Rx FIFO empty? */

  if (!(lsr & 0x01))
    {
      return 0;
    }

  *ch = readb(UART_REG(uart, RHR));
  //printf("getchar_nb(%u) = %02x\n", uart, *ch);
  return 1;
}

int uart_tx_busy(uint8_t uart)
{
  if (readb(UART_REG(uart, SSR)) & 0x01)
    {
      return 1;
    }

  return 0;
}

static const uint16_t divider[] =
{
  [UART_38400]  = 21,  /*   38,690 */
  [UART_57600]  = 14,  /*   58,035 */
  [UART_115200]  = 7,  /*  116,071 */
  [UART_230400]  = 4,  /*  203,125! (-3% would be 223,488) */
  [UART_460800]  = 2,  /*  406,250! (-3% would be 446,976) */
  [UART_921600]  = 1,  /*  812,500! (-3% would be 893,952) */
};

int uart_baudrate(uint8_t uart, enum uart_baudrate bdrt)
{
  uint16_t div;

  if (bdrt > ARRAY_SIZE(divider))
    {
      return -1;
    }

  div = divider[bdrt];
  uart_set_lcr7bit(uart, 1);
  writeb(div & 0xff, UART_REG(uart, DLL));
  writeb(div >> 8, UART_REG(uart, DLH));
  uart_set_lcr7bit(uart, 0);

  return 0;
}
